// locutor
// (c) 2009-2010, Normen Müller, normen.mueller@gmail.com  
// $Id: C.scala 94 2010-06-12 15:35:36Z normen.mueller $
package locutor

import java.io.File

import scala.xml.XML.{loadFile}

import scaup.Views._

import scalaxx.diff.Command
import scalaxx.diff.Differ.{diff => sdiff}

import locutor.Views._
import locutor.rws.RewriteSystem
import locutor.rws.gm.GraphModel
import locutor.rws.rs.RuleSystem
import locutor.g.DependencyGraph
import locutor.g.ImpactGraph

/** Companion to <code>DocumentCollection</code> class.
 * 
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.5
 */
object DocumentCollection {
  def apply(name: String, interlink: Option[String], documents: (String, DocumentModel)*): DocumentCollection =
    if(documents == null)
      error("Documents may not be null.")
    else ((interlink: RuleSystem) => new DocumentCollection(name, interlink, (Map[File,DocumentModel]() /: documents)((m,d) => d match {
      case (document, model) if document.toFile.exists => m + {(document.toFile, model)}
      case _                                           => error("Document \""+d._1+"\" does not exist.")
    })))(interlink match {
      case Some(interlink) => RuleSystem.parse(interlink.toFile)
      case None            => RuleSystem.empty
    })  
}

/** A document collection.
 * 
 *  <p>Each document <code>d</code> within a collection <code>C</code> must have an associated document model <code>M</code> whereas
 *  the associated graph model <code>G</code> is extended by the structural node types <code>N<sub>syn</sub></code> and structural link types
 *  <code>L<sub>syn</sub></code> of <code>d</code>, such that <code>M(d) = < E, G join (N<sub>syn</sub>, L<sub>syn</sub>), R ></code>.</p>
 * 
 *  <p>For interlinking documents within the collection an <i>interlink rule system</i> must be provided.</p>
 * 
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.5
 * 
 *  @todo mapping from document types to document models, like, for example:
 *  <pre>
 *  val C = DocumentCollection(
 *    "wedding",
 *    new Set("etc/data/wedding/guests.xml", "etc/data/wedding/seating.xml"),
 *    Map[DocumentType, DocumentModel](
 *      documentTypeOf("etc/data/wedding/guests.xml") -> DocumentModel("wedding/guests.eq", "wedding/guests.rs"),
 *      DocumentType("seatings") -> DocumentModel("wedding/seating.eq"),
 *    )
 *  ) withInterlinkage "wedding/wedding.rs"
 *  </pre>
 * 
 *  @param name      collection name
 *  @param interlink interlink rule system
 *  @param documents list of documents with associated document models
 */
class DocumentCollection private [locutor] (name: String, interlink: RuleSystem, documents: Map[File, DocumentModel]) extends Iterable[(File,DocumentModel)] {
  import GrGen._
  
  /** Rewrite system of this collection 
   */
  lazy val rewriteSystem = ((structural: GraphModel) =>    
    ((new RewriteSystem(structural) /: documents.values)(_ join _.rewriteSystem)) join new RewriteSystem(interlink)
  )((new GraphModel(name) /: documents.keys)((g,d) => g join GraphModel.compute(loadFile(d)))) 
    
  /** Iterator over all (document, model) pairs contained in this collection.
   */
  def elements = documents.elements
  
  /** Semantic difference analysis
   *  @return a mapping from each document within the collection to its corresponding sequence of edit operations turning BASE into WORKING.
   */
  def analyze = (Map[File, List[Command]]() /: elements)((m,d) => d match {
    case (working,model) => m + {(working, if(working.vc.versioned) sdiff(model.equivSystem, working.vc.base.head, working) else Nil)}
  })
  
  /** Semantic dependency analysis
   * 
   *  @return a dependency graph on the collection
   * 
   *  @todo introduce an upper bound for GrGen runtime; otherwise endless loops could break the system
   */                                               
  def annotate = ((structural: DependencyGraph) =>
    markup(name, structural, rewriteSystem withAnnotations)
  )((DependencyGraph.empty /: elements)((g, d) => d match {case (working,model) => g join DependencyGraph.compute(working)}))
  
  /** Change impact analysis
   * 
   *  @return an impact graph on the collection
   * 
   *  @todo introduce an upper bound for GrGen runtime; otherwise endless loops could break the system
   */
  def ripple(dependencyGraph: DependencyGraph, deltas: Map[File, List[Command]]) =
    if((deltas == null) || (List.flatten(deltas.values.toList) isEmpty))
      ImpactGraph.empty
    else if(dependencyGraph == null)
      error("Dependency graph may not be null.")
    else markup(name, ((ImpactGraph(dependencyGraph) /: deltas)(_ mark _)), rewriteSystem withRippling)

  /** Change impact analysis
   * 
   *  @see #ripple(DependencyGraph, Map[File, List[Command]])
   */
  def ↩(deltas: Map[File, List[Command]])(implicit dependencyGraph: DependencyGraph) = ripple(dependencyGraph, deltas)  
} 
